package cn.tk.engine.mr;

import cn.tk.cache.mr.FlowBuf;
import cn.tk.cache.mr.IntentBuf;
import cn.tk.cache.redis.RedisConstant;
import cn.tk.model.elastic.MrSessionRound;
import cn.tk.model.entity.mr.Flow;
import cn.tk.model.entity.mr.FlowNode;
import cn.tk.model.entity.mr.OutcallCollect;
import cn.tk.model.pojo.mr.bot.ProductBot;
import cn.tk.model.pojo.mr.engine.*;
import cn.tk.model.pojo.mr.flow.*;
import cn.tk.model.pojo.mr.nlu.NluResp;
import cn.tk.service.elastic.MrSessionService;
import cn.tk.service.log.queue.OutcallCollectQueue;
import cn.tk.service.mr.EntityNodeService;
import cn.tk.service.mr.ProductBotService;
import cn.tk.service.mr.ReplyNodeService;
import cn.tk.service.mr.SessionService;
import cn.tk.utils.EmptyUtil;
import cn.tk.utils.ThreadUtil;
import cn.tk.utils.snowflake.IdWorker;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by denglw on 2021/8/9.<br/>
 * Desc: 多轮流程引擎
 */
@Component
@Slf4j
public class FlowEngine {

    @Autowired
    private ProductBotService productBotService;
    @Autowired
    private SessionService sessionService;
    @Autowired
    private ReplyNodeDeal replyNodeDeal;
    @Autowired
    private EntityNodeDeal entityNodeDeal;
    @Autowired
    private ConditionNodeDeal conditionNodeDeal;
    @Autowired
    private FuncNodeDeal funcNodeDeal;
    @Autowired
    private ReplyNodeService replyNodeService;
    @Autowired
    private EntityNodeService entityNodeService;
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private MrSessionService mrSessionService;

    /**
     * 多轮对话引擎.<br/>
     * @param nlu 模型结果
     * @param req 请求实体
     * @return 多轮回复
     */
    public MrReply doMr(NluResp nlu, MrReq req) {
        // 线程上下文保存
        MrReqContext.setContext(req);
        try {
            return this.doAction(nlu, req);
        } finally {
            MrSession.Round currRound = sessionService.fetchCurrRound();
            // 保存日志到es库
            ThreadUtil.pool().execute(() -> saveRound2Es(currRound));
            log.info("当前轮次请求[{}]的会话信息：[{}].", req, currRound);
            // 清除线程上下文
            MrReqContext.removeContext();
        }
    }

    private MrReply doAction(NluResp nlu, MrReq req) {
        // 初始化会话缓存及判断是否是首次进入
        this.initRound(nlu, req);
        // 查找上一轮次：如果不存在，说明初次进入，需要根据入参去判断流程，并跳入首节点；
        // 如果存在，代表正处于多轮会话中，直接跳转到上一轮次的节点处
        MrSession.Round lastRound = sessionService.fetchLastRound();
        MrReply mrReply = lastRound == null ?
                doStartSession(nlu) : doProgressSession(nlu, lastRound);
        if (mrReply == null) {
            return null;
        }
        // 解析
        this.changeMrReply(mrReply);
        // 缓存回复内容
        MrSession.Round currRound = sessionService.fetchCurrRound();
        currRound.setCustomReplies(mrReply.getCustomReplies());
        currRound.setFilterQuestions(mrReply.getFilterQuestions());
        sessionService.saveRound(currRound);
        // 保存采集结果
        if (req.isWaihuScene()) {
            this.saveOutcallCollect(mrReply);
        }
        return mrReply;
    }

    private void saveRound2Es(MrSession.Round round) {
        MrSessionRound entity = new MrSessionRound();
        MrReq mrReq = MrReqContext.getContext();
        String userId = mrReq.getUserId();
        String sessionId = mrReq.getSessionId();
        String messageId = round.getMessageId();
        entity.setId(userId + "&&" + sessionId + "&&" + messageId);
        entity.setUserId(userId);
        entity.setSessionId(sessionId);
        entity.setMessageId(messageId);
        entity.setMsg(round.getMsg());
        entity.setClarifyResult(JSON.toJSONString(round.getClarifyResult()));
        entity.setCollects(JSON.toJSONString(round.getCollects()));
        entity.setContextPairs(JSON.toJSONString(round.getContextPairs()));
        entity.setCustomReplies(JSON.toJSONString(round.getCustomReplies()));
        StringBuilder filterQuestions = new StringBuilder();
        EmptyUtil.nullToEmptyList(round.getFilterQuestions()).forEach(i -> filterQuestions.append("&&").append(i));
        entity.setFilterQuestions(filterQuestions.toString().replaceFirst("&&", ""));
        entity.setFlowId(round.getFlowId());
        entity.setNlu(JSON.toJSONString(round.getNlu()));
        StringBuilder nodeIds = new StringBuilder();
        EmptyUtil.nullToEmptyList(round.getNodeIds()).forEach(i -> nodeIds.append("&&").append(i));
        entity.setNodeIds(nodeIds.toString().replaceFirst("&&", ""));
        entity.setSerialNo(round.getSerialNo());
        entity.setSmsSend(round.getSmsSend());
        mrSessionService.save(entity);
    }

    /**
     * 异步保存轮次中采集的信息项.<br/>
     * @param mrReply 回复
     */
    private void saveOutcallCollect(MrReply mrReply) {
        OutcallCollect collect = new OutcallCollect();
        collect.setId(idWorker.nextId());
        MrReq mrReq = MrReqContext.getContext();
        collect.setUserId(mrReq.getUserId());
        collect.setSessionId(mrReq.getSessionId());
        collect.setMessageId(mrReq.getMessageId());
        collect.setMsg(mrReq.getMsg());
        collect.setSmsSend(mrReply.isSmsSend());
        collect.setEndSession(mrReply.isEndSession());
        MrSession.Round round = sessionService.fetchCurrRound();
        Map<String, String> collectMap = round.getCollects();
        if (!EmptyUtil.emptyMap(collectMap)) {
            collect.setIntentLabel(collectMap.get("intentLabel"));
            collect.setQuestionFlag(collectMap.get("questionFlag"));
            collect.setQuestionNo(collectMap.get("questionNo"));
            collect.setQuestionContent(collectMap.get("questionContent"));
            collect.setAnswerNo(collectMap.get("answerNo"));
            collect.setAnswerContent(collectMap.get("answerContent"));
        }
        collect.setCreateTime(LocalDateTime.now());
        OutcallCollectQueue.getInstance().add(collect);
    }

    /**
     * 变更返回话术的内在数据
     * @param reply 回复
     */
    private void changeMrReply(MrReply reply) {
        if (reply == null) {
            return;
        }
        List<ContextPair> pairs = sessionService.getContextPairs();
        List<ReplyDTO> customReplies = reply.getCustomReplies();
        for (ReplyDTO dto : EmptyUtil.nullToEmptyList(customReplies)) {
            // 只更新文本详情：语音详情中的变量值和实体值不在ai赋值
            List<TextReplyDetail> textDetails = dto.getTextReplyDetails();
            if (EmptyUtil.emptyColl(textDetails)) {
                continue;
            }
            for (TextReplyDetail detail : textDetails) {
                String entityCode = detail.getEntityCode();
                String variableEnName = detail.getVariableEnName();
                if (detail.isEntity()) {
                    List<String> entityValues = pairs.stream().filter(i -> Objects.equals(i.getName(), entityCode))
                            .map(ContextPair::getValue).collect(Collectors.toList());
                    // 获取最末一个
                    if (!EmptyUtil.emptyColl(entityValues)) {
                        detail.setEntityValue(entityValues.get(entityValues.size() - 1));
                    }
                }
                if (detail.isVariable()) {
                    List<String> variables = pairs.stream().filter(i -> Objects.equals(i.getName(), variableEnName))
                            .map(ContextPair::getValue).collect(Collectors.toList());
                    // 获取最末一个
                    if (!EmptyUtil.emptyColl(variables)) {
                        detail.setVariable(variables.get(variables.size() - 1));
                    }
                }
            }
        }
    }

    /**
     * 处理开始进入会话的逻辑.<br/>
     * @param nlu 模型结果
     * @return 回复
     */
    private MrReply doStartSession(NluResp nlu) {
        MrReq req = MrReqContext.getContext();
        // 寻找bot
        Long botId = this.findBotId(req, nlu);
        if (EmptyUtil.nullOrZero(botId)) {
            log.info("没有匹配的bot.");
            return null;
        }
        // 直接跳转到开始节点
        FlowBuf flowBuf = FlowBuf.getInstance();
        Flow startFlow = flowBuf.startFlow(botId);
        if (startFlow == null) {
            log.info("bot没有配置开始流程.");
            return null;
        }
        Long flowId = startFlow.getId();
        FlowNode startNode = flowBuf.startNode(flowId);
        if (startNode == null) {
            log.info("flow没有配置开始节点.");
            return null;
        }
        Long nodeId = startNode.getId();
        MrSession.Round currRound = sessionService.fetchCurrRound();
        currRound.setFlowId(flowId);
        currRound.appendNodeId(nodeId);
        sessionService.saveRound(currRound);
        // 开始节点限定话术节点和条件节点
        if (startNode.isReplyNode()) {
            return replyNodeDeal.explain(flowId, nodeId, 0);
        }
        // 如果开始节点是条件节点，强制进入
        if (startNode.isConditionNode()) {
            assert flowId != null;
            assert nodeId != null;
            return this.doRecursion(nlu, flowId, nodeId);
        }
        return null;
    }

    /**
     * 寻址bot.<br/>
     * @param req 请求
     * @param nlu 模型结果
     * @return bot
     */
    private Long findBotId(MrReq req, NluResp nlu) {
        Long botId = null;
        String robotCode = req.getRobotCode();
        String original = req.getOriginal();
        List<ProductBot> productBots = productBotService.getBots(robotCode, original);
        if (EmptyUtil.emptyColl(productBots)) {
            return null;
        }
        if (req.isKefuScene()) {
            // 需要根据nlu一级意图进行跳转
            NluResp.InnerIntent primaryIntent = nlu.getPrimaryIntent();
            if (primaryIntent == null) {
                return null;
            }
            String sceneIntentCode = primaryIntent.getIntent();
            IntentBuf intentBuf = IntentBuf.getInstance();
            for (ProductBot i : productBots) {
                String code = intentBuf.getCode(i.getSceneIntentId());
                boolean match = Objects.equals(sceneIntentCode, code);
                if (match) {
                    botId = i.getBotId();
                    break;
                }
            }
        }
        if (req.isWaihuScene()) {
            botId = productBots.get(0).getBotId();
        }
        return botId;
    }

    /**
     * 处理正在进行中的会话逻辑.<br/>
     * @param nlu 模型结果
     * @param lastRound 上一轮次信息
     * @return 回复
     */
    private MrReply doProgressSession(NluResp nlu, MrSession.Round lastRound) {
        Long flowId = lastRound.getFlowId();
        Long lastNodeId = lastRound.getLastNodeId(null);
        return this.doRecursion(nlu, flowId, lastNodeId);
    }

    /**
     * 递归查找答案.<br/>
     * @param nlu 模型结果
     * @param flowId 上一轮次所在flow
     * @param lastNodeId 上一个节点id
     * @return 答案
     */
    private MrReply doRecursion(NluResp nlu, Long flowId, long lastNodeId) {
        // 限制递归上限为5次
        MrReq req = MrReqContext.getContext();
        int recursionNum = sessionService.getAndIncrRecursionNum(req.getMessageId(), "flow");
        if (recursionNum > 5) {
            log.info("流程递归达到上限.");
            return this.bottomReply(flowId);
        }

        // 1、是否不响应
        MrSession.Round lastRound = sessionService.fetchLastRound();
        if (lastRound != null) {
            List<String> filters = EmptyUtil.nullToEmptyList(lastRound.getFilterQuestions());
            boolean match = filters.stream().anyMatch(i -> Objects.equals(i, req.getMsg()));
            if (match) {
                log.info("触发不响应问题.");
                MrReply mrReply = new MrReply();
                mrReply.setCustomReplies(new ArrayList<>());
                mrReply.setSilent(true);
                // 更新缓存
                MrSession.Round round = sessionService.fetchCurrRound();
                round.setFlowId(flowId);
                round.appendNodeId(lastNodeId);
                sessionService.saveRound(round);
                return mrReply;
            }
        }

        // 2、是否匹配到重述意图：直接返回重述前缀 + 上一轮次话术
        FlowBuf flowBuf = FlowBuf.getInstance();
        MrReply repeatIntentReply = this.repeatIntentReply(nlu, flowId);
        if (repeatIntentReply != null) {
            log.info("重述意图匹配.");
            // 更新缓存
            MrSession.Round round = sessionService.fetchCurrRound();
            round.setFlowId(flowId);
            round.appendNodeId(lastNodeId);
            sessionService.saveRound(round);
            return repeatIntentReply;
        }

        // 3、由于实体节点是复合节点，首先判断上一轮次是否在实体澄清阶段，是则直接跳入
        FlowNode flowNode = flowBuf.getFlowNode(flowId, lastNodeId);
        if (flowNode != null && flowNode.isEntityNode()) {
            // 上一轮定义：如果第一次进入，即上一轮次；如果是递归循环中，则是当前轮次
            MrSession.Round lround = recursionNum == 0 ? lastRound : sessionService.fetchCurrRound();
            ClarifyResult clarifyResult = lround.getClarifyResult();
            if (clarifyResult.isClarifyDoing()) {
                EntityNodeDTO entityNode = entityNodeService.entityNode(lastNodeId);
                if (entityNode != null) {
                    MrReply mrReply = this.handleEntityNode(nlu, entityNode);
                    if (mrReply != null) {
                        return mrReply;
                    } else {
                        return doRecursion(nlu, entityNode.getFlowId(), entityNode.getNodeId());
                    }
                }
            }
        }

        // 4、话术子节点处理
        List<Long> replyNodeIds = flowBuf.childReplyNodeIds(lastNodeId);
        if (!EmptyUtil.emptyColl(replyNodeIds)) {
            // 直接进入话术节点处理：判断话术节点是否需要跳转到另一话术节点
            Long replyNodeId = replyNodeIds.get(0);
            RecursionReplyNodeResult result = recursionReplyNode(flowId, replyNodeId);
            if (result.getMrReply() != null) {
                return result.getMrReply();
            } else {
                return doRecursion(nlu, result.getFlowId(), result.getNodeId());
            }
        }

        // 5、函数子节点处理
        List<Long> funcNodeIds = flowBuf.childFuncNodeIds(lastNodeId);
        if (!EmptyUtil.emptyColl(funcNodeIds)) {
            // 函数节点只能匹配一个进行处理
            Long funcNodeId = funcNodeIds.get(0);
            this.handleFuncNode(flowId, funcNodeId);
            // 直接递归: 避免寻址实体节点和条件节点，浪费计算资源
            return doRecursion(nlu, flowId, this.lastNodeId());
        }

        // 6、实体子节点处理
        List<Long> entityNodeIds = flowBuf.childEntityNodeIds(lastNodeId);
        if (!EmptyUtil.emptyColl(entityNodeIds)) {
            EntityNodeDTO entityNode = entityNodeDeal.searchChild(flowId, lastNodeId);
            if (entityNode != null) {
                MrReply mrReply = this.handleEntityNode(nlu, entityNode);
                if (mrReply != null) {
                    return mrReply;
                } else {
                    return doRecursion(nlu, entityNode.getFlowId(), entityNode.getNodeId());
                }
            }
        }

        // 7、条件子节点处理
        List<Long> conditionNodeIds = flowBuf.childConditionNodeIds(lastNodeId);
        if (!EmptyUtil.emptyColl(conditionNodeIds)) {
            ConditionNodeDTO conditionNode = conditionNodeDeal.searchChild(nlu, flowId, lastNodeId);
            if (conditionNode != null) {
                this.handleConditionNode(conditionNode);
                return doRecursion(nlu, conditionNode.getFlowId(), conditionNode.getNodeId());
            }
        }

        // 8、全局匹配实体节点进行处理
        EntityNodeDTO entityNode = entityNodeDeal.searchRecover(nlu, flowId);
        if (entityNode != null) {
            MrReply mrReply = this.handleEntityNode(nlu, entityNode);
            if (mrReply != null) {
                return mrReply;
            } else {
                return doRecursion(nlu, entityNode.getFlowId(), entityNode.getNodeId());
            }
        }

        // 9、全局匹配条件节点进行处理
        ConditionNodeDTO conditionNode = conditionNodeDeal.searchRecover(nlu, flowId, lastNodeId);
        if (conditionNode != null) {
            this.handleConditionNode(conditionNode);
            return doRecursion(nlu, conditionNode.getFlowId(), conditionNode.getNodeId());
        }
        // 如果逻辑走到此处：代表处理失败了，返回兜底话术
        return this.bottomReply(flowId);
    }

    private Long lastNodeId() {
        MrSession.Round currRound = sessionService.fetchCurrRound();
        return currRound.getLastNodeId(null);
    }

    /**
     * 获取全局的重述回复.<br/>
     * @param nlu 模型结果
     * @param flowId 流程id
     * @return 回复
     */
    private MrReply repeatIntentReply(NluResp nlu, Long flowId) {
        if (nlu.getSecondaryIntent() == null) {
            return null;
        }
        String secondIntent = nlu.getSecondaryIntent().getIntent();
        // 是否匹配到重述意图：直接返回重述前缀 + 上一轮次话术
        Map<Integer, List<ReplyNodeDeal.InnerReply>> globalReplies = replyNodeDeal.getGlobalReplies(flowId);
        if (EmptyUtil.emptyMap(globalReplies)) {
            return null;
        }

        List<ReplyNodeDeal.InnerReply> innerReplies = globalReplies.get(4);
        // 查看是否匹配
        for (ReplyNodeDeal.InnerReply i : EmptyUtil.nullToEmptyList(innerReplies)){
            Long intentId = i.getIntentId();
            String intentCode = IntentBuf.getInstance().getCode(intentId);
            if (!Objects.equals(secondIntent, intentCode)) {
                continue;
            }
            ReplyDTO iReply = i.getReply();
            // 查找上一轮次回复: 如果上一轮回复中已经拼接了重述前缀，则忽略
            MrSession.Round lastRound = sessionService.fetchLastRound();
            if (lastRound == null) {
                continue;
            }
            List<ReplyDTO> customReplies = lastRound.getCustomReplies();
            Long headReplyId = customReplies.size() == 0 ? null : customReplies.get(0).getReplyId();
            if (!Objects.equals(headReplyId, iReply.getReplyId())) {
                customReplies.add(0, iReply);
            }
            MrReply mrReply = MrReply.defaultReply();
            mrReply.setCustomReplies(customReplies);
            return mrReply;
        }
        return null;
    }

    /**
     * 递归解答话术节点.<br/>
     * @param flowId 流程id
     * @param replyNodeId 话术节点id
     * @return 回复
     */
    private RecursionReplyNodeResult recursionReplyNode(Long flowId, Long replyNodeId) {
        // 限制递归上限为5次
        MrReq req = MrReqContext.getContext();
        int recursionNum = sessionService.getAndIncrRecursionNum(req.getMessageId(), "replyNode");
        if (recursionNum > 5) {
            log.info("话术节点递归达到上限.");
            return new RecursionReplyNodeResult();
        }

        MrSession.Round round = sessionService.fetchCurrRound();
        // 获取该节点已经流转的次数
        int repeatNum = sessionService.getRepeatNum(replyNodeId);
        // 缓存
        round.setFlowId(flowId);
        round.appendNodeId(replyNodeId);
        sessionService.saveRound(round);
        ReplyNodeJump jump = replyNodeDeal.jumpOut(replyNodeId);
        if (jump.isIfJump()) {
            Long jumpFlowId = jump.getJumpFlowId();
            Long jumpNodeId = jump.getJumpNodeId();
            return this.recursionReplyNode(jumpFlowId, jumpNodeId);
        }
        MrReply mrReply = replyNodeDeal.explain(flowId, replyNodeId, repeatNum);
        // 更新缓存
        if (mrReply != null) {
            round.setSmsSend(mrReply.isSmsSend());
            sessionService.saveRound(round);
        }
        RecursionReplyNodeResult result = new RecursionReplyNodeResult();
        result.setMrReply(mrReply);
        result.setFlowId(flowId);
        result.setNodeId(replyNodeId);
        return result;
    }

    /**
     * 函数节点处理逻辑.<br/>
     * @param flowId 流程id
     * @param funcNodeId 函数节点id
     */
    private void handleFuncNode(Long flowId, Long funcNodeId) {
        MrSession.Round round = sessionService.fetchCurrRound();
        // 更新缓存
        round.setFlowId(flowId);
        round.appendNodeId(funcNodeId);
        sessionService.saveRound(round);
        // 求解函数
        List<ContextPair> contextPairs = sessionService.getContextPairs();
        JSONObject json = funcNodeDeal.funcCall(funcNodeId, contextPairs);
        if (json == null || json.isEmpty()) {
            return;
        }
        // 将返回结果字段设置到缓存中
        Set<Map.Entry<String, Object>> entries = json.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            ContextPair pair = new ContextPair();
            pair.setName(entry.getKey());
            pair.setValue(convert2Str(entry.getValue()));
            pair.setType(3);
            round.appendContextPair(pair);
        }
        sessionService.saveRound(round);
    }

    private String convert2Str(Object value) {
        if (value == null) {
            return "";
        }
        if (value instanceof Number) {
            return value + "";
        }
        if (value instanceof String) {
            return (String) value;
        }
        return JSON.toJSONString(value);
    }

    /**
     * 实体节点澄清，并最终封装话术返回.<br/>
     * @param nlu 模型结果
     * @param entityNode 实体节点
     * @return 回复
     */
    private MrReply handleEntityNode(NluResp nlu, EntityNodeDTO entityNode) {
        // 限制递归上限为2次
        MrReq req = MrReqContext.getContext();
        int recursionNum = sessionService.getAndIncrRecursionNum(req.getMessageId(), "entityNode");
        if (recursionNum > 2) {
            log.info("实体节点递归达到上限.");
            return bottomReply(entityNode.getFlowId());
        }

        // 缓存
        MrSession.Round round = sessionService.fetchCurrRound();
        Long flowId = entityNode.getFlowId();
        round.setFlowId(flowId);
        Long entityNodeId = entityNode.getNodeId();
        round.appendNodeId(entityNodeId);
        List<ClarifyItem> clarifyItems = sessionService.getClarifyItems(entityNodeId);
        ClarifyResult clarify = entityNodeDeal.clarify(nlu, entityNode, clarifyItems);
        assert clarify != null;
        round.setClarifyResult(clarify);
        sessionService.saveRound(round);

        // 1、如果澄清失败
        if (clarify.isClarifyError()) {
            // 查看是否能匹配到其他实体节点
            EntityNodeDTO searchRecover = entityNodeDeal.searchRecover(nlu, flowId);
            if (searchRecover != null) {
                return handleEntityNode(nlu, searchRecover);
            }
            // 如果没有配置达到上限回复话术，则进行兜底，否则，返回上限话术
            if (EmptyUtil.nullOrZero(clarify.getClarifyReplyId())) {
                return bottomReply(flowId);
            }
        }
        // 2、如果澄清成功: 直接返回null，进行节点递归
        if (clarify.isClarifySuccess()) {
            return null;
        }
        // 3、澄清中：封装澄清话术
        Long clarifyReplyId = clarify.getClarifyReplyId();
        ReplyDTO reply = replyNodeService.getReply(clarifyReplyId);
        MrReply mrReply = new MrReply();
        mrReply.setCustomReplies(Lists.newArrayList(reply));
        return mrReply;
    }

    /**
     * 条件节点逻辑处理.<br/>
     * @param conditionNode 条件节点
     */
    private void handleConditionNode(ConditionNodeDTO conditionNode) {
        MrSession.Round round = sessionService.fetchCurrRound();
        round.setFlowId(conditionNode.getFlowId());
        round.appendNodeId(conditionNode.getNodeId());
        // 信息采集
        List<ConditionNodeCollect> collects = conditionNode.getConditionNodeCollects();
        Map<String, String> map = new HashMap<>(8);
        for (ConditionNodeCollect collect : EmptyUtil.nullToEmptyList(collects)) {
            map.put(collect.getCode(), collect.getValue());
        }
        round.setCollects(map);
        sessionService.saveRound(round);
    }

    /**
     * 获取兜底话术.<br/>
     * @param flowId 流程id
     * @return 兜底话术
     */
    private MrReply bottomReply(Long flowId) {
        log.info("进入多轮兜底匹配.");
        Map<Integer, List<ReplyNodeDeal.InnerReply>> map = replyNodeDeal.getGlobalReplies(flowId);
        if (EmptyUtil.emptyMap(map)) {
            return null;
        }
        List<ReplyNodeDeal.InnerReply> replyList = map.get(1);
        if (EmptyUtil.emptyColl(replyList)) {
            return null;
        }
        MrReply bottom = new MrReply();
        bottom.setEndSession(true);
        // 随机返回一条兜底话术
        int idx = new Random().nextInt(replyList.size());
        List<ReplyDTO> replies = new ArrayList<>();
        replies.add(replyList.get(idx).getReply());
        bottom.setCustomReplies(replies);
        return bottom;
    }

    /**
     * 初始化缓存轮次信息.<br/>
     * @param nlu 模型结果
     * @param req 请求
     */
    private void initRound(NluResp nlu, MrReq req) {
        MrSession.Round round = new MrSession.Round(req.getMessageId());
        round.setMsg(req.getMsg());
        round.setNlu(nlu);
        // nlu结果处理：实体澄清结果设置到上下文中
        List<NluResp.InnerEntity> entities = nlu.getEntities();
        if (!EmptyUtil.emptyColl(entities)) {
            for (NluResp.InnerEntity entity : entities) {
                String entityCode = entity.getEntityCode();
                // 澄清值
                String entityValue = entity.getValue();
                if (EmptyUtil.blank(entityValue)) {
                    continue;
                }
                ContextPair pair = new ContextPair(entityCode, entityValue, 2);
                round.appendContextPair(pair);
            }
        }
        // 将original设置到上下文中
        ContextPair pair = new ContextPair("original", req.getOriginal(), 1);
        round.appendContextPair(pair);
        round.setContextPairs(EmptyUtil.nullToEmptyList(round.getContextPairs()));
        // 设置默认值
        round.setNodeIds(new ArrayList<>());
        round.setCollects(new HashMap<>(8));
        round.setClarifyResult(new ClarifyResult());
        round.setCustomReplies(new ArrayList<>());
        round.setFilterQuestions(new ArrayList<>());
        round.setSmsSend(false);
        synchronized (sessionLock(req)) {
            int maxSerialNo = sessionService.maxSerialNo();
            round.setSerialNo(maxSerialNo + 1);
            sessionService.saveRound(round);
        }
    }

    private String sessionLock(MrReq req) {
        String userId = req.getUserId();
        String sessionId = req.getSessionId();
        return (RedisConstant.MR_SESSION +  userId + RedisConstant.SPLIT + sessionId).intern();
    }

    /**
     * 递归话术节点返回结果类
     */
    @Data @NoArgsConstructor
    static class RecursionReplyNodeResult {
        /**
         * 回复
         */
        private MrReply mrReply;

        /**
         * 当前所在流程id
         */
        private Long flowId;

        /**
         * 当前所在话术节点id
         */
        private Long nodeId;
    }

}
